/**
 * Copyright 2010 Victor Egea Hernando
 *
 * RockScript is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 * 
 * RockScript is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with RockScript.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <ctime>
#include <cstdlib>
#include <iostream>
#include <paper/paper.h>
#include <corelib/corelib.h>

// VM handler definition
class vm_test1_handler : public paper::vmachine_hnd
{

public:
	// dynamic error report
    void dynError( const paper::vm_error &error )
	{
		std::cout << "error: " << error.error() << std::endl;
	}

	// 
    void open( const corelib::ustring &path )
	{

	}

	// 
    void compile( const corelib::ustring &path )
	{

	}

	// 
    corelib::uint8 getc( void )
	{

	}

	// 
    void close()
	{

	}

};

class test{

public:
	//
	int main();
	// 
	int load_class_method();
	//
	int load_code_miscellanea();

protected:
	// VM & handler
	paper::vmachine vm;
	vm_test1_handler hnd;
	// classes and methods
	paper::vm_class* _class;
	paper::method* _method1;
	paper::method* _method2;

};

int test::main(){
	// set handler
	vm.set_hnd(hnd);

	int ret = 0;
	// load classes and methods
	if(ret = load_class_method() | load_code_miscellanea()){
		return ret;
	}

	reginteger( vm.argument( 1 ) ) = 13833;
	reginteger( vm.argument( 2 ) ) = 17013;

	// call function
	if(!vm.run(_class->name(),_method2->meta_name())){
		return -1;
	}

	std::cout << "lcm(13833,17013): " << reginteger( vm.lret() ) << std::endl;
	return 0;
}

int test::load_class_method(){
	/** 
	 * add class
	 */
	corelib::pair<paper::vm_class*, bool> testc = vm.add_class( "test.Test" );
	// check addition
	if(!testc.second){ return -1; }
	// get class
	_class = testc.first;

	/** 
	 * add methods
	 */
	corelib::pair<paper::method*, bool> testm1 = _class->add_method( "gdc", "mthd_2_", 2 );
	// check addition
	if( !testm1.second ){ return -1; }
	// get class
	_method1 = testm1.first;

	corelib::pair<paper::method*, bool> testm2 = _class->add_method( "lcm", "mthd_2_", 2 );
	// check addition
	if( !testm2.second ){ return -1; }
	// get class
	_method2 = testm2.first;	

	return 0;
}

int test::load_code_miscellanea(){
	/* 
	 * Binary GCD algorithm {u, v}
	 */

	//allocate memory
	if(	!_method1->set_stack_frame_size( 0, 4 ) ){
		std::cout << "error allocating method1 memory" << std::endl;
		return -1;
	}

	// set method static
	_method1->set_flags( paper::FLG_MTHD_STATIC );
	// load constants and set local variables
	_method1->add_inst_b(paper::OP_LOADIZ,11,0); // R11 = 0 (shift)
	_method1->add_inst_b(paper::OP_LOADIZ,14,1); // R14 = 1
	_method1->add_inst_b(paper::OP_LOADIZ,15,0); // R15 = 0
	// if ( u == 0 || v == 0 ) return u | v;
	_method1->add_inst_a(paper::OP_EQI,1,15,0);
	_method1->add_inst_b(paper::OP_RCJMP,0,3);
	_method1->add_inst_a(paper::OP_NEQI,2,15,0);
	_method1->add_inst_b(paper::OP_RCJMP,0,3);
	_method1->add_inst_a(paper::OP_BWORI,12,1,2);
	_method1->add_inst_b(paper::OP_RETURN,12,0);
	// for (; ((u | v) & 1) == 0; ++shift)
	_method1->add_inst_a(paper::OP_BWORI,12,1,2);
	_method1->add_inst_a(paper::OP_BWANDI,12,12,14);
	_method1->add_inst_a(paper::OP_NEQI,12,15,0);
	_method1->add_inst_b(paper::OP_RCJMP,0,5);
	// u >>>= 1; v >>>= 1;
	_method1->add_inst_a(paper::OP_BWUSHRI,1,1,14);
	_method1->add_inst_a(paper::OP_BWUSHRI,2,2,14);
	_method1->add_inst_b(paper::OP_INCI,0,REG_TOOGLE(11));
	_method1->add_inst_b(paper::OP_RJMP,0,-7);
	// while ((u & 1) == 0) u >>>= 1;
	_method1->add_inst_a(paper::OP_BWANDI,4,1,14);
	_method1->add_inst_a(paper::OP_NEQI,4,15,0);
	_method1->add_inst_b(paper::OP_RCJMP,0,3);
	_method1->add_inst_a(paper::OP_BWUSHRI,1,1,14);
	_method1->add_inst_b(paper::OP_RJMP,0,-4);
	// do{ while ((v & 1) == 0) v >>>= 1;
	_method1->add_inst_a(paper::OP_BWANDI,12,2,14);
	_method1->add_inst_a(paper::OP_NEQI,12,15,0);
	_method1->add_inst_b(paper::OP_RCJMP,0,3);
	_method1->add_inst_a(paper::OP_BWUSHRI,2,2,14);
	_method1->add_inst_b(paper::OP_RJMP,0,-4);
	// if (u < v) { v -= u; }
	_method1->add_inst_a(paper::OP_GEI,1,2,0);
	_method1->add_inst_b(paper::OP_RCJMP,0,3);
	_method1->add_inst_a(paper::OP_SUBI,2,2,1);
	_method1->add_inst_b(paper::OP_RJMP,0,4);
	// else { int32 diff = u - v; u = v; v = diff; }
	_method1->add_inst_a(paper::OP_SUBI,13,1,2);
	_method1->add_inst_a(paper::OP_MOVE,1,2,0);
	_method1->add_inst_a(paper::OP_MOVE,2,13,0);
	// v >>>= 1;
	_method1->add_inst_a(paper::OP_BWUSHRI,2,2,14);
	// } while (v != 0);
	_method1->add_inst_a(paper::OP_NEQI,2,15,0);
	_method1->add_inst_b(paper::OP_RCJMP,0,-14);
	// return u << shift;
	_method1->add_inst_a(paper::OP_BWSHLI,12,1,3);
	_method1->add_inst_b(paper::OP_RETURN,12,0);

	/* 
	 * Least common multiple
	 */

	// allocate memory
	if(	!_method2->set_stack_frame_size( 0, 4 ) ){
		std::cout << "error allocating method2 memory" << std::endl;
		return( -1 );
	}

	// set method static
	_method2->set_flags( paper::FLG_MTHD_STATIC );
	// R11 = a * b
	_method2->add_inst_a(paper::OP_MULI,11,1,2);
	// R10 = method1(a,b)
	_method2->add_inst_a(paper::OP_LOADARG,1,1,0);
	_method2->add_inst_a(paper::OP_LOADARG,2,2,0);
	_method2->add_inst_b(paper::OP_KCCALL, 
		_method1->idx(),_method1->pclass()->idx());
	_method2->add_inst_a(paper::OP_DIVI,10,11,10); // R10 = R11 / R10 
	// return R10;
	_method2->add_inst_b(paper::OP_RETURN,10,0);

	return 0;
}

int main(char *argv[], int argc)
{
	test test1;
	return test1.main();
}
